home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / sendmail / sendmail-5.65 / src / headers.c < prev    next >
Encoding:
C/C++ Source or Header  |  1990-06-05  |  17.3 KB  |  851 lines

  1. /*
  2.  * Copyright (c) 1983 Eric P. Allman
  3.  * Copyright (c) 1988 Regents of the University of California.
  4.  * All rights reserved.
  5.  *
  6.  * Redistribution and use in source and binary forms are permitted provided
  7.  * that: (1) source distributions retain this entire copyright notice and
  8.  * comment, and (2) distributions including binaries display the following
  9.  * acknowledgement:  ``This product includes software developed by the
  10.  * University of California, Berkeley and its contributors'' in the
  11.  * documentation or other materials provided with the distribution and in
  12.  * all advertising materials mentioning features or use of this software.
  13.  * Neither the name of the University nor the names of its contributors may
  14.  * be used to endorse or promote products derived from this software without
  15.  * specific prior written permission.
  16.  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
  17.  * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
  18.  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  19.  */
  20.  
  21. #ifndef lint
  22. static char sccsid[] = "@(#)headers.c    5.15 (Berkeley) 6/1/90";
  23. #endif /* not lint */
  24.  
  25. # include <sys/param.h>
  26. # include <errno.h>
  27. # include "sendmail.h"
  28.  
  29. /*
  30. **  CHOMPHEADER -- process and save a header line.
  31. **
  32. **    Called by collect and by readcf to deal with header lines.
  33. **
  34. **    Parameters:
  35. **        line -- header as a text line.
  36. **        def -- if set, this is a default value.
  37. **
  38. **    Returns:
  39. **        flags for this header.
  40. **
  41. **    Side Effects:
  42. **        The header is saved on the header list.
  43. **        Contents of 'line' are destroyed.
  44. */
  45.  
  46. chompheader(line, def)
  47.     char *line;
  48.     bool def;
  49. {
  50.     register char *p;
  51.     register HDR *h;
  52.     HDR **hp;
  53.     char *fname;
  54.     char *fvalue;
  55.     struct hdrinfo *hi;
  56.     bool cond = FALSE;
  57.     BITMAP mopts;
  58.     extern char *crackaddr();
  59.  
  60.     if (tTd(31, 6))
  61.         printf("chompheader: %s\n", line);
  62.  
  63.     /* strip off options */
  64.     clrbitmap(mopts);
  65.     p = line;
  66.     if (*p == '?')
  67.     {
  68.         /* have some */
  69.         register char *q = index(p + 1, *p);
  70.         
  71.         if (q != NULL)
  72.         {
  73.             *q++ = '\0';
  74.             while (*++p != '\0')
  75.                 setbitn(*p, mopts);
  76.             p = q;
  77.         }
  78.         else
  79.             usrerr("chompheader: syntax error, line \"%s\"", line);
  80.         cond = TRUE;
  81.     }
  82.  
  83.     /* find canonical name */
  84.     fname = p;
  85.     p = index(p, ':');
  86.     if (p == NULL)
  87.     {
  88.         syserr("chompheader: syntax error, line \"%s\"", line);
  89.         return (0);
  90.     }
  91.     fvalue = &p[1];
  92.     while (isspace(*--p))
  93.         continue;
  94.     *++p = '\0';
  95.     makelower(fname);
  96.  
  97.     /* strip field value on front */
  98.     if (*fvalue == ' ')
  99.         fvalue++;
  100.  
  101.     /* see if it is a known type */
  102.     for (hi = HdrInfo; hi->hi_field != NULL; hi++)
  103.     {
  104.         if (strcmp(hi->hi_field, fname) == 0)
  105.             break;
  106.     }
  107.  
  108.     /* see if this is a resent message */
  109.     if (!def && bitset(H_RESENT, hi->hi_flags))
  110.         CurEnv->e_flags |= EF_RESENT;
  111.  
  112.     /* if this means "end of header" quit now */
  113.     if (bitset(H_EOH, hi->hi_flags))
  114.         return (hi->hi_flags);
  115.  
  116.     /* drop explicit From: if same as what we would generate -- for MH */
  117.     p = "resent-from";
  118.     if (!bitset(EF_RESENT, CurEnv->e_flags))
  119.         p += 7;
  120.     if (!def && !QueueRun && strcmp(fname, p) == 0)
  121.     {
  122.         if (CurEnv->e_from.q_paddr != NULL &&
  123.             strcmp(fvalue, CurEnv->e_from.q_paddr) == 0)
  124.             return (hi->hi_flags);
  125.     }
  126.  
  127.     /* delete default value for this header */
  128.     for (hp = &CurEnv->e_header; (h = *hp) != NULL; hp = &h->h_link)
  129.     {
  130.         if (strcmp(fname, h->h_field) == 0 &&
  131.             bitset(H_DEFAULT, h->h_flags) &&
  132.             !bitset(H_FORCE, h->h_flags))
  133.             h->h_value = NULL;
  134.     }
  135.  
  136.     /* create a new node */
  137.     h = (HDR *) xalloc(sizeof *h);
  138.     h->h_field = newstr(fname);
  139.     h->h_value = NULL;
  140.     h->h_link = NULL;
  141.     bcopy((char *) mopts, (char *) h->h_mflags, sizeof mopts);
  142.     *hp = h;
  143.     h->h_flags = hi->hi_flags;
  144.     if (def)
  145.         h->h_flags |= H_DEFAULT;
  146.     if (cond)
  147.         h->h_flags |= H_CHECK;
  148.     if (h->h_value != NULL)
  149.         free((char *) h->h_value);
  150.     h->h_value = newstr(fvalue);
  151.  
  152.     /* hack to see if this is a new format message */
  153.     if (!def && bitset(H_RCPT|H_FROM, h->h_flags) &&
  154.         (index(fvalue, ',') != NULL || index(fvalue, '(') != NULL ||
  155.          index(fvalue, '<') != NULL || index(fvalue, ';') != NULL))
  156.     {
  157.         CurEnv->e_flags &= ~EF_OLDSTYLE;
  158.     }
  159.  
  160.     return (h->h_flags);
  161. }
  162. /*
  163. **  ADDHEADER -- add a header entry to the end of the queue.
  164. **
  165. **    This bypasses the special checking of chompheader.
  166. **
  167. **    Parameters:
  168. **        field -- the name of the header field.
  169. **        value -- the value of the field.  It must be lower-cased.
  170. **        e -- the envelope to add them to.
  171. **
  172. **    Returns:
  173. **        none.
  174. **
  175. **    Side Effects:
  176. **        adds the field on the list of headers for this envelope.
  177. */
  178.  
  179. addheader(field, value, e)
  180.     char *field;
  181.     char *value;
  182.     ENVELOPE *e;
  183. {
  184.     register HDR *h;
  185.     register struct hdrinfo *hi;
  186.     HDR **hp;
  187.  
  188.     /* find info struct */
  189.     for (hi = HdrInfo; hi->hi_field != NULL; hi++)
  190.     {
  191.         if (strcmp(field, hi->hi_field) == 0)
  192.             break;
  193.     }
  194.  
  195.     /* find current place in list -- keep back pointer? */
  196.     for (hp = &e->e_header; (h = *hp) != NULL; hp = &h->h_link)
  197.     {
  198.         if (strcmp(field, h->h_field) == 0)
  199.             break;
  200.     }
  201.  
  202.     /* allocate space for new header */
  203.     h = (HDR *) xalloc(sizeof *h);
  204.     h->h_field = field;
  205.     h->h_value = newstr(value);
  206.     h->h_link = *hp;
  207.     h->h_flags = hi->hi_flags | H_DEFAULT;
  208.     clrbitmap(h->h_mflags);
  209.     *hp = h;
  210. }
  211. /*
  212. **  HVALUE -- return value of a header.
  213. **
  214. **    Only "real" fields (i.e., ones that have not been supplied
  215. **    as a default) are used.
  216. **
  217. **    Parameters:
  218. **        field -- the field name.
  219. **
  220. **    Returns:
  221. **        pointer to the value part.
  222. **        NULL if not found.
  223. **
  224. **    Side Effects:
  225. **        none.
  226. */
  227.  
  228. char *
  229. hvalue(field)
  230.     char *field;
  231. {
  232.     register HDR *h;
  233.  
  234.     for (h = CurEnv->e_header; h != NULL; h = h->h_link)
  235.     {
  236.         if (!bitset(H_DEFAULT, h->h_flags) && strcmp(h->h_field, field) == 0)
  237.             return (h->h_value);
  238.     }
  239.     return (NULL);
  240. }
  241. /*
  242. **  ISHEADER -- predicate telling if argument is a header.
  243. **
  244. **    A line is a header if it has a single word followed by
  245. **    optional white space followed by a colon.
  246. **
  247. **    Parameters:
  248. **        s -- string to check for possible headerness.
  249. **
  250. **    Returns:
  251. **        TRUE if s is a header.
  252. **        FALSE otherwise.
  253. **
  254. **    Side Effects:
  255. **        none.
  256. */
  257.  
  258. bool
  259. isheader(s)
  260.     register char *s;
  261. {
  262.     while (*s > ' ' && *s != ':' && *s != '\0')
  263.         s++;
  264.  
  265.     /* following technically violates RFC822 */
  266.     while (isspace(*s))
  267.         s++;
  268.  
  269.     return (*s == ':');
  270. }
  271. /*
  272. **  EATHEADER -- run through the stored header and extract info.
  273. **
  274. **    Parameters:
  275. **        e -- the envelope to process.
  276. **
  277. **    Returns:
  278. **        none.
  279. **
  280. **    Side Effects:
  281. **        Sets a bunch of global variables from information
  282. **            in the collected header.
  283. **        Aborts the message if the hop count is exceeded.
  284. */
  285.  
  286. eatheader(e)
  287.     register ENVELOPE *e;
  288. {
  289.     register HDR *h;
  290.     register char *p;
  291.     int hopcnt = 0;
  292.  
  293.     if (tTd(32, 1))
  294.         printf("----- collected header -----\n");
  295.     for (h = e->e_header; h != NULL; h = h->h_link)
  296.     {
  297.         extern char *capitalize();
  298.  
  299.         if (tTd(32, 1))
  300.             printf("%s: %s\n", capitalize(h->h_field), h->h_value);
  301.         /* count the number of times it has been processed */
  302.         if (bitset(H_TRACE, h->h_flags))
  303.             hopcnt++;
  304.  
  305.         /* send to this person if we so desire */
  306.         if (GrabTo && bitset(H_RCPT, h->h_flags) &&
  307.             !bitset(H_DEFAULT, h->h_flags) &&
  308.             (!bitset(EF_RESENT, CurEnv->e_flags) || bitset(H_RESENT, h->h_flags)))
  309.         {
  310.             sendtolist(h->h_value, (ADDRESS *) NULL, &CurEnv->e_sendqueue);
  311.         }
  312.  
  313.         /* log the message-id */
  314. #ifdef LOG
  315.         if (!QueueRun && LogLevel > 8 && h->h_value != NULL &&
  316.             strcmp(h->h_field, "message-id") == 0)
  317.         {
  318.             char buf[MAXNAME];
  319.  
  320.             p = h->h_value;
  321.             if (bitset(H_DEFAULT, h->h_flags))
  322.             {
  323.                 expand(p, buf, &buf[sizeof buf], e);
  324.                 p = buf;
  325.             }
  326.             syslog(LOG_INFO, "%s: message-id=%s", e->e_id, p);
  327.         }
  328. #endif LOG
  329.     }
  330.     if (tTd(32, 1))
  331.         printf("----------------------------\n");
  332.  
  333.     /* store hop count */
  334.     if (hopcnt > e->e_hopcount)
  335.         e->e_hopcount = hopcnt;
  336.  
  337.     /* message priority */
  338.     p = hvalue("precedence");
  339.     if (p != NULL)
  340.         e->e_class = priencode(p);
  341.     if (!QueueRun)
  342.         e->e_msgpriority = e->e_msgsize
  343.                  - e->e_class * WkClassFact
  344.                  + e->e_nrcpts * WkRecipFact;
  345.  
  346.     /* return receipt to */
  347.     p = hvalue("return-receipt-to");
  348.     if (p != NULL)
  349.         e->e_receiptto = p;
  350.  
  351.     /* errors to */
  352.     p = hvalue("errors-to");
  353.     if (p != NULL)
  354.         sendtolist(p, (ADDRESS *) NULL, &e->e_errorqueue);
  355.  
  356.     /* from person */
  357.     if (OpMode == MD_ARPAFTP)
  358.     {
  359.         register struct hdrinfo *hi = HdrInfo;
  360.  
  361.         for (p = NULL; p == NULL && hi->hi_field != NULL; hi++)
  362.         {
  363.             if (bitset(H_FROM, hi->hi_flags))
  364.                 p = hvalue(hi->hi_field);
  365.         }
  366.         if (p != NULL)
  367.             setsender(p);
  368.     }
  369.  
  370.     /* full name of from person */
  371.     p = hvalue("full-name");
  372.     if (p != NULL)
  373.         define('x', p, e);
  374.  
  375.     /* date message originated */
  376.     p = hvalue("posted-date");
  377.     if (p == NULL)
  378.         p = hvalue("date");
  379.     if (p != NULL)
  380.     {
  381.         define('a', p, e);
  382.         /* we don't have a good way to do canonical conversion ....
  383.         define('d', newstr(arpatounix(p)), e);
  384.         .... so we will ignore the problem for the time being */
  385.     }
  386.  
  387.     /*
  388.     **  Log collection information.
  389.     */
  390.  
  391. # ifdef LOG
  392.     if (!QueueRun && LogLevel > 1)
  393.     {
  394.         char hbuf[100], *name = hbuf;
  395.  
  396.         if (RealHostName == NULL)
  397.             name = "local";
  398.         else if (RealHostName[0] == '[')
  399.             name = RealHostName;
  400.         else
  401.             (void)sprintf(hbuf, "%.90s (%s)", 
  402.                 RealHostName, inet_ntoa(RealHostAddr.sin_addr));
  403.         syslog(LOG_INFO,
  404.             "%s: from=%s, size=%ld, class=%d, received from %s\n",
  405.             CurEnv->e_id, CurEnv->e_from.q_paddr, CurEnv->e_msgsize,
  406.             CurEnv->e_class, name);
  407.     }
  408. # endif LOG
  409. }
  410. /*
  411. **  PRIENCODE -- encode external priority names into internal values.
  412. **
  413. **    Parameters:
  414. **        p -- priority in ascii.
  415. **
  416. **    Returns:
  417. **        priority as a numeric level.
  418. **
  419. **    Side Effects:
  420. **        none.
  421. */
  422.  
  423. priencode(p)
  424.     char *p;
  425. {
  426.     register int i;
  427.  
  428.     for (i = 0; i < NumPriorities; i++)
  429.     {
  430.         if (!strcasecmp(p, Priorities[i].pri_name))
  431.             return (Priorities[i].pri_val);
  432.     }
  433.  
  434.     /* unknown priority */
  435.     return (0);
  436. }
  437. /*
  438. **  CRACKADDR -- parse an address and turn it into a macro
  439. **
  440. **    This doesn't actually parse the address -- it just extracts
  441. **    it and replaces it with "$g".  The parse is totally ad hoc
  442. **    and isn't even guaranteed to leave something syntactically
  443. **    identical to what it started with.  However, it does leave
  444. **    something semantically identical.
  445. **
  446. **    The process is kind of strange.  There are a number of
  447. **    interesting cases:
  448. **        1.  comment <address> comment    ==> comment <$g> comment
  449. **        2.  address            ==> address
  450. **        3.  address (comment)        ==> $g (comment)
  451. **        4.  (comment) address        ==> (comment) $g
  452. **    And then there are the hard cases....
  453. **        5.  add (comment) ress        ==> $g (comment)
  454. **        6.  comment <address (comment)>    ==> comment <$g (comment)>
  455. **        7.    .... etc ....
  456. **
  457. **    Parameters:
  458. **        addr -- the address to be cracked.
  459. **
  460. **    Returns:
  461. **        a pointer to the new version.
  462. **
  463. **    Side Effects:
  464. **        none.
  465. **
  466. **    Warning:
  467. **        The return value is saved in local storage and should
  468. **        be copied if it is to be reused.
  469. */
  470.  
  471. char *
  472. crackaddr(addr)
  473.     register char *addr;
  474. {
  475.     register char *p;
  476.     register int i;
  477.     static char buf[MAXNAME];
  478.     char *rhs;
  479.     bool gotaddr;
  480.     register char *bp;
  481.  
  482.     if (tTd(33, 1))
  483.         printf("crackaddr(%s)\n", addr);
  484.  
  485.     (void) strcpy(buf, "");
  486.     rhs = NULL;
  487.  
  488.     /* strip leading spaces */
  489.     while (*addr != '\0' && isspace(*addr))
  490.         addr++;
  491.  
  492.     /*
  493.     **  See if we have anything in angle brackets.  If so, that is
  494.     **  the address part, and the rest is the comment.
  495.     */
  496.  
  497.     p = index(addr, '<');
  498.     if (p != NULL)
  499.     {
  500.         /* copy the beginning of the addr field to the buffer */
  501.         *p = '\0';
  502.         (void) strcpy(buf, addr);
  503.         (void) strcat(buf, "<");
  504.         *p++ = '<';
  505.  
  506.         /* skip spaces */
  507.         while (isspace(*p))
  508.             p++;
  509.  
  510.         /* find the matching right angle bracket */
  511.         addr = p;
  512.         for (i = 0; *p != '\0'; p++)
  513.         {
  514.             switch (*p)
  515.             {
  516.               case '<':
  517.                 i++;
  518.                 break;
  519.  
  520.               case '>':
  521.                 i--;
  522.                 break;
  523.             }
  524.             if (i < 0)
  525.                 break;
  526.         }
  527.  
  528.         /* p now points to the closing quote (or a null byte) */
  529.         if (*p != '\0')
  530.         {
  531.             /* make rhs point to the extra stuff at the end */
  532.             rhs = p;
  533.             *p++ = '\0';
  534.         }
  535.     }
  536.  
  537.     /*
  538.     **  Now parse the real address part.  "addr" points to the (null
  539.     **  terminated) version of what we are inerested in; rhs points
  540.     **  to the extra stuff at the end of the line, if any.
  541.     */
  542.  
  543.     p = addr;
  544.  
  545.     /* now strip out comments */
  546.     bp = &buf[strlen(buf)];
  547.     gotaddr = FALSE;
  548.     for (; *p != '\0'; p++)
  549.     {
  550.         if (*p == '(')
  551.         {
  552.             /* copy to matching close paren */
  553.             *bp++ = *p++;
  554.             for (i = 0; *p != '\0'; p++)
  555.             {
  556.                 *bp++ = *p;
  557.                 switch (*p)
  558.                 {
  559.                   case '(':
  560.                     i++;
  561.                     break;
  562.  
  563.                   case ')':
  564.                     i--;
  565.                     break;
  566.                 }
  567.                 if (i < 0)
  568.                     break;
  569.             }
  570.             continue;
  571.         }
  572.  
  573.         /*
  574.         **  If this is the first "real" character we have seen,
  575.         **  then we put the "$g" in the buffer now.
  576.         */
  577.  
  578.         if (isspace(*p))
  579.             *bp++ = *p;
  580.         else if (!gotaddr)
  581.         {
  582.             (void) strcpy(bp, "\001g");
  583.             bp += 2;
  584.             gotaddr = TRUE;
  585.         }
  586.     }
  587.  
  588.     /* hack, hack.... strip trailing blanks */
  589.     do
  590.     {
  591.         *bp-- = '\0';
  592.     } while (isspace(*bp));
  593.     bp++;
  594.  
  595.     /* put any right hand side back on */
  596.     if (rhs != NULL)
  597.     {
  598.         *rhs = '>';
  599.         (void) strcpy(bp, rhs);
  600.     }
  601.  
  602.     if (tTd(33, 1))
  603.         printf("crackaddr=>`%s'\n", buf);
  604.  
  605.     return (buf);
  606. }
  607. /*
  608. **  PUTHEADER -- put the header part of a message from the in-core copy
  609. **
  610. **    Parameters:
  611. **        fp -- file to put it on.
  612. **        m -- mailer to use.
  613. **        e -- envelope to use.
  614. **
  615. **    Returns:
  616. **        none.
  617. **
  618. **    Side Effects:
  619. **        none.
  620. */
  621.  
  622. putheader(fp, m, e)
  623.     register FILE *fp;
  624.     register MAILER *m;
  625.     register ENVELOPE *e;
  626. {
  627.     char buf[MAX(MAXFIELD,BUFSIZ)];
  628.     register HDR *h;
  629.     extern char *arpadate();
  630.     extern char *capitalize();
  631.     char obuf[MAX(MAXFIELD,MAXLINE)];
  632.  
  633.     for (h = e->e_header; h != NULL; h = h->h_link)
  634.     {
  635.         register char *p;
  636.         extern bool bitintersect();
  637.  
  638.         if (bitset(H_CHECK|H_ACHECK, h->h_flags) &&
  639.             !bitintersect(h->h_mflags, m->m_flags))
  640.             continue;
  641.  
  642.         /* handle Resent-... headers specially */
  643.         if (bitset(H_RESENT, h->h_flags) && !bitset(EF_RESENT, e->e_flags))
  644.             continue;
  645.  
  646.         p = h->h_value;
  647.         if (bitset(H_DEFAULT, h->h_flags))
  648.         {
  649.             /* macro expand value if generated internally */
  650.             expand(p, buf, &buf[sizeof buf], e);
  651.             p = buf;
  652.             if (p == NULL || *p == '\0')
  653.                 continue;
  654.         }
  655.  
  656.         if (bitset(H_FROM|H_RCPT, h->h_flags))
  657.         {
  658.             /* address field */
  659.             bool oldstyle = bitset(EF_OLDSTYLE, e->e_flags);
  660.  
  661.             if (bitset(H_FROM, h->h_flags))
  662.                 oldstyle = FALSE;
  663.             commaize(h, p, fp, oldstyle, m);
  664.         }
  665.         else
  666.         {
  667.             /* vanilla header line */
  668.             register char *nlp;
  669.  
  670.             (void) sprintf(obuf, "%s: ", capitalize(h->h_field));
  671.             while ((nlp = index(p, '\n')) != NULL)
  672.             {
  673.                 *nlp = '\0';
  674.                 (void) strcat(obuf, p);
  675.                 *nlp = '\n';
  676.                 putline(obuf, fp, m);
  677.                 p = ++nlp;
  678.                 obuf[0] = '\0';
  679.             }
  680.             (void) strcat(obuf, p);
  681.             putline(obuf, fp, m);
  682.         }
  683.     }
  684. }
  685. /*
  686. **  COMMAIZE -- output a header field, making a comma-translated list.
  687. **
  688. **    Parameters:
  689. **        h -- the header field to output.
  690. **        p -- the value to put in it.
  691. **        fp -- file to put it to.
  692. **        oldstyle -- TRUE if this is an old style header.
  693. **        m -- a pointer to the mailer descriptor.  If NULL,
  694. **            don't transform the name at all.
  695. **
  696. **    Returns:
  697. **        none.
  698. **
  699. **    Side Effects:
  700. **        outputs "p" to file "fp".
  701. */
  702.  
  703. commaize(h, p, fp, oldstyle, m)
  704.     register HDR *h;
  705.     register char *p;
  706.     FILE *fp;
  707.     bool oldstyle;
  708.     register MAILER *m;
  709. {
  710.     register char *obp;
  711.     int opos;
  712.     bool firstone = TRUE;
  713.     char obuf[MAXLINE + 3];
  714.  
  715.     /*
  716.     **  Output the address list translated by the
  717.     **  mailer and with commas.
  718.     */
  719.  
  720.     if (tTd(14, 2))
  721.         printf("commaize(%s: %s)\n", h->h_field, p);
  722.  
  723.     obp = obuf;
  724.     (void) sprintf(obp, "%s: ", capitalize(h->h_field));
  725.     opos = strlen(h->h_field) + 2;
  726.     obp += opos;
  727.  
  728.     /*
  729.     **  Run through the list of values.
  730.     */
  731.  
  732.     while (*p != '\0')
  733.     {
  734.         register char *name;
  735.         char savechar;
  736.         extern char *remotename();
  737.         extern char *DelimChar;        /* defined in prescan */
  738.  
  739.         /*
  740.         **  Find the end of the name.  New style names
  741.         **  end with a comma, old style names end with
  742.         **  a space character.  However, spaces do not
  743.         **  necessarily delimit an old-style name -- at
  744.         **  signs mean keep going.
  745.         */
  746.  
  747.         /* find end of name */
  748.         while (isspace(*p) || *p == ',')
  749.             p++;
  750.         name = p;
  751.         for (;;)
  752.         {
  753.             char *oldp;
  754.             char pvpbuf[PSBUFSIZE];
  755.             extern bool isatword();
  756.             extern char **prescan();
  757.  
  758.             (void) prescan(p, oldstyle ? ' ' : ',', pvpbuf);
  759.             p = DelimChar;
  760.  
  761.             /* look to see if we have an at sign */
  762.             oldp = p;
  763.             while (*p != '\0' && isspace(*p))
  764.                 p++;
  765.  
  766.             if (*p != '@' && !isatword(p))
  767.             {
  768.                 p = oldp;
  769.                 break;
  770.             }
  771.             p += *p == '@' ? 1 : 2;
  772.             while (*p != '\0' && isspace(*p))
  773.                 p++;
  774.         }
  775.         /* at the end of one complete name */
  776.  
  777.         /* strip off trailing white space */
  778.         while (p >= name && (isspace(*p) || *p == ',' || *p == '\0'))
  779.             p--;
  780.         if (++p == name)
  781.             continue;
  782.         savechar = *p;
  783.         *p = '\0';
  784.  
  785.         /* translate the name to be relative */
  786.         name = remotename(name, m, bitset(H_FROM, h->h_flags), FALSE);
  787.         if (*name == '\0')
  788.         {
  789.             *p = savechar;
  790.             continue;
  791.         }
  792.  
  793.         /* output the name with nice formatting */
  794.         opos += qstrlen(name);
  795.         if (!firstone)
  796.             opos += 2;
  797.         if (opos > 78 && !firstone)
  798.         {
  799.             (void) strcpy(obp, ",\n");
  800.             putline(obuf, fp, m);
  801.             obp = obuf;
  802.             (void) sprintf(obp, "        ");
  803.             opos = strlen(obp);
  804.             obp += opos;
  805.             opos += qstrlen(name);
  806.         }
  807.         else if (!firstone)
  808.         {
  809.             (void) sprintf(obp, ", ");
  810.             obp += 2;
  811.         }
  812.  
  813.         /* strip off quote bits as we output */
  814.         while (*name != '\0' && obp < &obuf[MAXLINE])
  815.         {
  816.             if (bitset(0200, *name))
  817.                 *obp++ = '\\';
  818.             *obp++ = *name++ & ~0200;
  819.         }
  820.         firstone = FALSE;
  821.         *p = savechar;
  822.     }
  823.     (void) strcpy(obp, "\n");
  824.     putline(obuf, fp, m);
  825. }
  826. /*
  827. **  ISATWORD -- tell if the word we are pointing to is "at".
  828. **
  829. **    Parameters:
  830. **        p -- word to check.
  831. **
  832. **    Returns:
  833. **        TRUE -- if p is the word at.
  834. **        FALSE -- otherwise.
  835. **
  836. **    Side Effects:
  837. **        none.
  838. */
  839.  
  840. bool
  841. isatword(p)
  842.     register char *p;
  843. {
  844.     extern char lower();
  845.  
  846.     if (lower(p[0]) == 'a' && lower(p[1]) == 't' &&
  847.         p[2] != '\0' && isspace(p[2]))
  848.         return (TRUE);
  849.     return (FALSE);
  850. }
  851.